// decoder.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include <fstream>
#include <iostream>
#include <string>
#include <sstream>
#include <unordered_map>
#include <iterator>
#include <stdio.h>
#include <bitset>
#include <algorithm> 
#include <functional> 
#include <cctype>
#include <locale>

using namespace std;

class Profiler{
public:
	clock_t ticker;
	double elapse;
	void start(){
		ticker = clock();
	}
	void stop(){
		clock_t end = clock();
		elapse = (double)(end - ticker) / CLOCKS_PER_SEC;
		cout << "it took " << elapse << " seconds to execute" << endl;
	}
};

typedef unsigned char BYTE;
unordered_map<string,char> strcodes;
char _c;
/*stackoverflow.com/questions/2844817/how-do-i-check-if-a-c-string-is-an-int*/
inline bool isInteger(const string &s)
{
   if(s.empty() || ((!isdigit(s[0])) && (s[0] != '-') && (s[0] != '+'))) return false ;
   char * p ;
   strtol(s.c_str(), &p, 10) ;
   return (*p == 0) ;
}

static inline string &rtrim(std::string &s) {
        s.erase(find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
        return s;
}

bitset<8> ToBits(unsigned char byte)
{
    return bitset<8>(byte);
}

//bro draper format specs
char replaceChar(string s){
	if(s=="space"){
		return 32;
	}else if(s=="return"){
		return 13;
	}else if(s=="newline"){
		return 10;
	}else if(s=="tab"){
		return 9;
	}else{
		return s[0];
	}
}


bool searchMap(const string bits, unordered_map<string,char> &codes){
	unordered_map<string,char>::iterator it;
	if ((it = codes.find(bits)) != codes.end()){
        _c = it->second;
		//codes.erase(it);
		return true;
	}else{
		return false;
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
        //what if the user forgot to include a command line parameter?
        if (argc < 2) {
                cout << "missing command-line parameter" << endl;
                exit(1);
        }

        //attempt to open  file
        ifstream infile;
		infile.open(argv[1],ios::in | ios::binary);
        if (!infile) {
                cout << "Cannot find input file." << endl;
                exit(1);
        }

        /*stackoverflow.com/questions/6291458/how-to-convert-tchar-array-to-stdstring*/
        #ifndef UNICODE  
          typedef std::string String;
        #else
          typedef std::wstring String;
        #endif
       
        /*stackoverflow.com/questions/4804298/how-to-convert-wstring-into-string*/
        String f = argv[1];
        string iFilename(f.begin(), f.end());

		
		unordered_map<string,char> strcodes;
		vector<string> arr;
		unsigned int totalBits = 0;
		bool foundTotalBits = false;
		bool nextLineisttlBits = false;
		//generate the look up table with ascii char codes as lookup key


		Profiler* prof = new Profiler();
		prof->start();
		cout << "profiling..." << endl;
		string str;
		string line;
		cout << "reading file contents +  decoding...";
		while(!infile.eof()){

			if(!foundTotalBits){
				getline(infile, line,'\n');
			}
			line = rtrim(line);
			if(line != "*****" && !foundTotalBits && !nextLineisttlBits){
				istringstream iss(line);
				copy(istream_iterator<string>(iss),
						 istream_iterator<string>(),
						 back_inserter<vector<string>>(arr));
				if(arr.size()<2){
					nextLineisttlBits = true;
					continue;
				}
				strcodes[arr[0]] = replaceChar(arr[1]) ;
				arr.clear();
			}else{
				if(line !="*****"){ //ignore this marker
					if(isInteger(line) && !foundTotalBits){ //only 1 comparison for isInteger
						totalBits = stoi(line);
						foundTotalBits = true;
					}else{ //binary data
						char c = infile.get();
						string bin = ToBits(c).to_string();
						str += bin;
					}
				}else{
					nextLineisttlBits = true;
				}
			}
		}

		cout << "done" << endl;

		string tmp;
		string allstring;


		for(int i = 0; i < totalBits;i++){
			tmp+=str[i];
			if(searchMap(tmp,strcodes)){
				allstring+=_c;
				tmp="";
			}
		}

		string ff = iFilename.substr(0,iFilename.length()-3); //strip the extension of the file name;
		string ext = iFilename.substr(iFilename.length()-3,3);
		iFilename+="2"+ext; //append zip301 extension;

		ofstream out;
		out.open(ff); 
		if( !out.is_open()) 
			out.open(iFilename, ios_base::in | ios_base::out | ios_base::trunc);

		cout << "writing to file...";

		out << allstring;
		out.close();

		cout << "done" << endl;

		prof->stop();


}